<template>
  <div>
    <span class="mr-ls">
      附件分片长度：
      <a-input-number v-model:value="chunkSize">
        <template #addonAfter>
          <span>MB</span>
        </template>
      </a-input-number>
    </span>
  </div>
  <a-divider />
  <lib-a-table :tableOption="tableOption" :tableColumns="tableColumns" ref="tableList" :rowSelection="rowSelection">
    <template #tablePrefixSlot>
      <div class="upload-container">
        <a-button type="primary">
          <icon-font type="icon-download" />
          上传附件
          <input type="file" multiple="multiple" class="upload-input" @change="handleFileChange($event)" />
        </a-button>
      </div>
    </template>
    <template #tableSuffixSlot>
      <a-button type="default" @click="downloadMore()" class="mr-ms">
        <icon-font type="icon-download" />
        批量下载
      </a-button>
      <a-button type="primary" @click="deleteMore()" danger>
        <icon-font type="icon-delete" />
        批量删除
      </a-button>
    </template>
    <template #OriginName="data">
      <a-input v-if="data.rowData && data.rowData['Id'] === curRowId" v-model:value="data.rowData['OriginName']" />
      <template v-else>
        <a-tooltip placement="top" title="预览文件">
          <a href="javascript:void(0)" @click="previewFile(data.rowData)">
            {{ data.value }}
          </a>
        </a-tooltip>
      </template>
    </template>
    <template #operation="data">
      <template v-if="data.rowData && data.rowData['Id'] === curRowId">
        <a-tooltip placement="top" title="保存">
          <a href="javascript:void(0)" @click="edit(data.rowData)" class="icon mr-ms">
            <icon-font type="icon-save" />
          </a>
        </a-tooltip>
        <a-tooltip placement="top" title="取消">
          <a href="javascript:void(0)" class="icon" @click="edit()">
            <icon-font type="icon-undo" />
          </a>
        </a-tooltip>
      </template>
      <template v-else>
        <a-tooltip placement="top" title="下载">
          <a href="javascript:void(0)" @click="dowloadFile(data.rowData)" class="icon mr-ms"><icon-font type="icon-download" /></a>
        </a-tooltip>
        <a-tooltip placement="top" title="重命名">
          <a href="javascript:void(0)" @click="renameFile(data.rowData)" class="icon mr-ms"><icon-font type="icon-edit" /></a>
        </a-tooltip>
        <a-tooltip placement="top" title="删除">
          <a href="javascript:void(0)" class="icon" style="color: red" @click="deleteFile(data.rowData)"><icon-font type="icon-delete" /></a>
        </a-tooltip>
      </template>
    </template>
  </lib-a-table>
  <template v-if="curPercent < 100 && curPercent > 0">
    <a-divider />
    <div class="process">
      <a-progress :percent="curPercent" status="active" class="mr-ls" />
      <a href="javascript:void" @click="playUpload" v-if="curStatus === 1"><play-circle-outlined /></a>
      <a href="javascript:void" @click="pauseUpload" v-if="curStatus === 2"><pause-circle-outlined /></a>
    </div>
  </template>
  <a-divider />
  <div class="hash-time">
    <div v-for="(item, index) of hashType" :key="index" class="hash">
      <div style="font-size: 16px; font-weight: 400">{{ item.name }}</div>
      <a-progress type="circle" :percent="100" :format="() => item.time" style="margin-right: 20px" />
    </div>
  </div>
  <a-divider />
  <component v-if="isShowModal" :is="'LibAModal'" :title="'预览文件'" :width="1000" @close-modal="closeModal">
    <template #modalContent>
      <pdf-view :fileId="curRowFile.Id" v-if="previewFileType.isPdf"></pdf-view>
      <xlsx-view :fileId="curRowFile.Id" v-if="previewFileType.isXlsx"></xlsx-view>
      <docx-view :fileId="curRowFile.Id" :fileType="fileType" v-if="previewFileType.isDocx"></docx-view>
    </template>
    <template #footer>
      <a-button @click="closeModal(false)">
        <icon-font type="icon-cancel" />
        关 闭
      </a-button>
      <a-button type="primary" @click="dowloadFile(curRowFile)">
        <icon-font type="icon-download" />
        下 载
      </a-button>
    </template>
  </component>
</template>
<script>
import { defineComponent, reactive, ref, watch, computed } from 'vue';
import LibATable from '@/components/LibATable/index';
import LibAModal from '@/components/LibAModal/index';
import PdfView from './components/PdfView.vue';
import DocxView from './components/DocxView.vue';
import XlsxView from './components/XlsxView.vue';
import { PlayCircleOutlined, PauseCircleOutlined } from '@ant-design/icons-vue';
import { saveAttachment, deleteAttach, downloadAttach, uploadAttachment } from '@/api/index.js';
import { message } from 'ant-design-vue';
import Util from '@/utils/util.js';
import UploadStatus from './upload-status';
import { getFileHash, getFileHashWorker, getFileHashSample, getFileHashWorkerSample } from './hash';

export default defineComponent({
  components: {
    LibATable,
    LibAModal,
    PdfView,
    DocxView,
    XlsxView,
    PlayCircleOutlined,
    PauseCircleOutlined,
  },
  props: {},
  setup() {
    const chunkSize = ref(2);
    const CHUNK_SIZE = chunkSize.value * 1024 * 1024;
    const tableList = ref(null);
    const curStatus = ref(2);
    let curPercent = 0;
    const isShowModal = ref(false);
    const curRowFile = ref({});
    const previewFileType = reactive({
      isPdf: false,
      isXlsx: false,
      isDocx: false,
    });
    let hashType = [
      {
        name: 'spark-MD5',
        time: 0,
      },
      {
        name: 'spark-MD5+worker',
        time: 0,
      },
      {
        name: 'sample',
        time: 0,
      },
      {
        name: 'sample+worker',
        time: 0,
      },
    ];
    const container = reactive({
      file: {},
      hash: '',
      fileChunks: [],
    });
    const rowSelection = reactive({
      checkStrictly: false,
      selectedRowKeys: [],
      selectedRows: [],
      onChange: (selectedRowKeys, selectedRows) => {
        if (rowSelection.selectedRowKeys.length > 0) {
          rowSelection.selectedRowKeys = [];
        }
        if (rowSelection.selectedRows.length > 0) {
          rowSelection.selectedRows = [];
        }
        rowSelection.selectedRows = selectedRows;
        rowSelection.selectedRowKeys = selectedRowKeys;
      },
    });
    const tableOption = reactive({
      url: '/api/Train/Attachment/GetList',
      scroll: { x: 1024 },
      tableName: 'user-list',
      showExportBtn: false,
    });
    const curRowId = ref(null);
    const fileType = ref(null);
    const tableColumns = reactive([
      {
        title: '文件名',
        dataIndex: 'OriginName',
        slot: 'custom', // 注意这个是name1，与templates中的slot对应
      },
      {
        title: '文件大小',
        dataIndex: 'FileByteLength',
        width: '120px',
      },
      {
        title: '上传时间',
        dataIndex: 'CreateTime',
        width: '150px',
        slot: 'date',
      },
      {
        title: '操作',
        width: '110px',
        fixed: 'right',
        slot: 'operation',
      },
    ]);

    curPercent = computed(() => {
      if (!container.file || container.fileChunks.length <= 0) return 0;
      const list = container.fileChunks.map((item) => {
        // 防止中断的时候会出现NAN
        const result = item.size * item.progress;
        if (isNaN(result)) {
          return 0;
        } else {
          return result;
        }
      });
      const loaded = list.reduce((acc, cur) => {
        return acc + cur;
      }, 0);
      return parseInt((loaded / container.file.size).toFixed(2));
    });

    watch(curPercent, (now) => {
      if (curPercent < now) {
        curPercent = now;
      }
    });

    const refresh = () => {
      tableList.value.refresh();
    };

    const dowloadFile = (data) => {
      downloadAttach({ id: data.Id }).then((res) => {
        const blob = new Blob([res.data]); // 将响应数据处理为Blob类型
        const url = window.URL.createObjectURL(blob); // 创建一个URL对象
        var a = document.createElement('a'); // 创建一个a标签
        a.href = url;
        a.download = data.OriginName; // 这里指定下载文件的文件名
        a.click();
        window.URL.revokeObjectURL(url); // 释放之前创建的URL对象
      });
    };

    const renameFile = (entity) => {
      curRowId.value = entity.Id;
      // refresh();
    };

    const deleteFile = (entity) => {
      deleteAttach({
        id: entity.Id,
        hash: entity.FileHash,
        type: entity.FileType,
      }).then((item) => {
        if (item.Status === 200) {
          message.success('删除成功！');
          refresh();
        }
      });
    };

    const handleFileChange = (event) => {
      // 浅复制了原数组中的元素的一个新数组
      const file = Array.prototype.slice.call(event.target.files);
      if (file.length === 0) return;
      file.forEach((item) => {
        container.file = item;
        curPercent = 0;
        handleUpload();
      });
    };

    // 创建文件分片
    const createFileChunk = (file, size = CHUNK_SIZE) => {
      const chunks = [];
      let cur = 0;
      while (cur < file.size) {
        chunks.push({ file: file.slice(cur, cur + size) });
        cur += size;
      }
      return chunks;
    };

    const getFileByteFormat = (num) => {
      if (num > 1024 * 1024 * 1024) {
        return (num / (1024 * 1024 * 1024)).toFixed(2) + 'GB';
      }
      if (num > 1024 * 1024) {
        return (num / (1024 * 1024)).toFixed(2) + 'MB';
      }
      if (num > 1024) {
        return (num / 1024).toFixed(2) + 'KB';
      }
      return num + 'B';
    };

    // 保存文件的信息
    const saveAttachmentInfo = async () => {
      if (!container.file) return;
      const { name, size, type } = container.file;
      const data = {
        FileHash: container.hash,
        OriginName: name,
        FileType: type,
        FileByteLength: getFileByteFormat(size),
        FileSize: size,
      };
      await saveAttachment(data);
      refresh();
    };

    // 合并分片
    const mergeChunks = async () => {
      await uploadAttachment('MergeChunk', {
        fileName: container.file.name,
        size: CHUNK_SIZE,
        fileHash: container.hash,
      })
        .then(() => {
          curPercent = 100;
          // 保存附件相关信息
          saveAttachmentInfo();
        })
        .catch(() => {});
    };

    const setChunkPrecent = (item) => {
      return (ev) => {
        item.progress = parseInt(String((ev.loaded / ev.total) * 100));
      };
    };

    const uploadChunksRequest = (
      chunksList = {
        form: null,
        index: -1,
        status: -1,
        done: false,
      },
      max = 4,
      retrys = 3
    ) => {
      return new Promise((resolve, reject) => {
        const len = chunksList.length;
        let counter = 0;
        const retryArr = [];
        const start = async () => {
          while (counter < len && max > 0) {
            max--;
            const i = chunksList.findIndex((v) => +v.status === UploadStatus.wait || v.status === UploadStatus.error);

            if (chunksList[i]) {
              chunksList[i].status = UploadStatus.uploading;
            }
            let form;
            let index;
            if (i >= 0) {
              form = chunksList[i].form;
              index = chunksList[i].index;
            }
            // if (typeof retryArr[index] === "number") {
            //   console.log(index, "开始重试");
            // }
            await uploadAttachment('UploadChunk', form, {
              onUploadProgress: setChunkPrecent(container.fileChunks[index]),
            })
              .then((data) => {
                chunksList[i].status = UploadStatus.done;
                max++;
                counter++;
                chunksList[i].done = true;
                if (curStatus.value === UploadStatus.pause) {
                  return resolve(counter);
                }
                if (counter === len) {
                  resolve(counter);
                } else {
                  start();
                }
              })
              .catch((err) => {
                if (i >= 0) {
                  chunksList[i].status = UploadStatus.error;
                }
                if (index >= 0) {
                  // if (typeof retryArr[index] !== 'number') {
                  //   retryArr[index] = 0;
                  // }
                  // retryArr[index]++;
                  // if (retryArr[index] >= 2) {
                  //   reject();
                  // }
                  container.fileChunks[index].progress = -1; // 报错的进度条
                }
                max++;
                start();
              });
          }
        };
        if (counter < len) {
          start();
        }
      });
    };

    // 上传分片
    const uploadChunks = async (uploadedList) => {
      let curfileChunks = [];
      // 判断已上传的文件数组长度是否等于分片数组长度
      if (uploadedList.length === container.fileChunks.length) {
        return message.warn('文件已存在，不可重复上传！');
      }
      if (Array.isArray(uploadedList) && uploadedList.length > 0) {
        // 过滤取未上传分片数组
        curfileChunks = container.fileChunks.filter((chunk) => uploadedList.indexOf(chunk.chunkName) === -1);
      } else {
        // 没有已上传分片
        curfileChunks = container.fileChunks;
      }
      // 对分片数组进行一一遍历存相关文件信息到formData
      const list = curfileChunks.map((item, index) => {
        const form = new FormData();
        form.append('chunk', item.chunk);
        form.append('chunkName', item.chunkName);
        if (container.file) {
          form.append('fileName', container.file.name);
        }
        form.append('fileHash', item.fileHash);
        return { form, index, status: UploadStatus.wait, done: false };
      });
      // FormData是一种特殊的对象类型，不可序列化，因此使用console.log()直接打印显示的是FormData原型
      try {
        // 上传分片
        let ret = await uploadChunksRequest(list, 4);
        if (ret > 0 && ret === container.fileChunks.length) {
          // 上传和已经存在之和等于全部的再合并
          await mergeChunks(container.file);
        }
      } catch (err) {
        console.log(err);
      }
    };

    const verifyFileChunk = async (fileName, chunkHashName) => {
      const data = await uploadAttachment('Verify', { fileName, chunkHashName });
      return data.Data;
    };

    const moreHashType = async () => {
      const begin1 = Date.now();
      const hash1 = await getFileHash(container.file, CHUNK_SIZE);
      const end1 = Date.now();
      hashType[0].time = end1 - begin1 + 'ms';

      const begin2 = Date.now();
      const hash2 = await getFileHashWorker(container.file, CHUNK_SIZE);
      const end2 = Date.now();
      hashType[1].time = end2 - begin2 + 'ms';

      const begin3 = Date.now();
      const hash3 = await getFileHashSample(container.file, CHUNK_SIZE);
      const end3 = Date.now();
      hashType[2].time = end3 - begin3 + 'ms';

      const begin4 = Date.now();
      const hash4 = await getFileHashWorkerSample(container.file, CHUNK_SIZE);
      const end4 = Date.now();
      hashType[3].time = end4 - begin4 + 'ms';
    };

    const handleUpload = async () => {
      if (!container.file) {
        message.error('请上传文件');
      }
      curStatus.value = UploadStatus.uploading;
      container.hash = await getFileHash(container.file, CHUNK_SIZE);
      // 不同hash消耗时间
      // await moreHashType();
      const chunks = createFileChunk(container.file);
      const { uploaded, uploadedList } = await verifyFileChunk(container.file.name, container.hash);
      if (uploaded) {
        message.error('文件已存在！');
      }
      container.fileChunks = chunks.map((chunk, index) => {
        const chunkName = container.hash + '-' + index;
        return {
          chunk: chunk.file,
          index,
          chunkName: chunkName,
          fileHash: container.hash,
          progress: 0,
          size: chunk.file.size,
        };
      });
      // 存入已经存在的切片清单
      await uploadChunks(uploadedList);
    };

    const playUpload = async () => {
      curStatus.value = UploadStatus.uploading;
      const { uploadedList } = await verifyFileChunk(container.file.name, container.hash);
      await uploadChunks(uploadedList);
    };

    const pauseUpload = () => {
      curStatus.value = UploadStatus.pause;
    };

    const edit = (data) => {
      if (Util.isUndefinedOrNull(data)) {
        curRowId.value = '';
      } else {
        saveAttachment(data).then(() => {
          message.success('重命名成功！');
          curRowId.value = '';
          refresh();
        });
      }
    };

    const previewFile = async (data) => {
      curRowFile.value = data;
      let originName = data.OriginName;
      fileType.value = originName.slice(originName.indexOf('.'), originName.length);
      if (/\.(doc|docx){1}$/.test(fileType.value)) {
        previewFileType.isDocx = true;
        isShowModal.value = true;
      } else if (/\.pdf{1}$/.test(fileType.value)) {
        previewFileType.isPdf = true;
        isShowModal.value = true;
      } else if (/\.(xlsx|xls){1}$/.test(fileType.value)) {
        previewFileType.isXlsx = true;
        isShowModal.value = true;
      } else {
        message.warn('暂时不支持预览当前文件！');
      }
      // await getFileById({ id: data.Id }).then((item) => {
      //   const fileUrl = item.Data;
      //   window.open(fileUrl);
      //   // 在线预览 这里需要在局域网下进行访问的
      //   // window.open('https://view.xdocin.com/view?src=' + encodeURIComponent(fileUrl));
      // });
    };

    const downloadMore = () => {
      if (rowSelection.selectedRowKeys.length > 0) {
        rowSelection.selectedRows.forEach((item) => {
          dowloadFile(item);
        });
      } else {
        message.warn('请选择要下载的附件！');
      }
    };

    const deleteMore = () => {
      if (rowSelection.selectedRowKeys.length > 0) {
        rowSelection.selectedRows.forEach((item) => {
          deleteFile(item);
        });
      } else {
        message.warn('请选择要删除的附件！');
      }
    };

    const closeModal = (data) => {
      isShowModal.value = data;
    };

    return {
      chunkSize,
      CHUNK_SIZE,
      tableList,
      tableOption,
      tableColumns,
      curPercent,
      curRowId,
      curRowFile,
      isShowModal,
      fileType,
      rowSelection,
      curStatus,
      hashType,
      previewFileType,
      closeModal,
      downloadMore,
      previewFile,
      deleteMore,
      edit,
      dowloadFile,
      renameFile,
      deleteFile,
      handleFileChange,
      handleUpload,
      playUpload,
      pauseUpload,
    };
  },
});
</script>
<style lang="less" scoped>
.upload-input {
  position: absolute;
  left: 0;
  width: 100%;
  opacity: 0;
  cursor: pointer;
  // z-index: -1;
}
::v-deep .ant-progress-line {
  display: inline-block !important;
  width: 95%;
}
.process {
  font-size: 25px;
  vertical-align: -webkit-baseline-middle;
  display: flex;
  align-items: center;
}
.icon {
  font-size: 20px;
}

.hash-time {
  display: flex;
  justify-content: center;
  align-items: center;

  .hash {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
  }
}
</style>
